using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using DslCrossModelFramework;
using DslCrossModelFramework.Environment;
using DslCrossModelFramework.VSPackage;
using EnvDTE;
using EnvDTE80;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;
using Microsoft.VisualStudio.Modeling.Validation;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.Win32;
using VSLangProj;

namespace DslCrossModelFramework {
    public class ModelFileHelper : IModelFileHelper {

        public ModelFileHelper() {
            assemblyReferenceHelper = new AssemblyReferenceHelper();
        }

        internal static object syncObject = new object();

        private static volatile AssemblyReferenceHelper assemblyReferenceHelper;
        internal static AssemblyReferenceHelper AssemblyReferenceHelper{
            get {
                return assemblyReferenceHelper;
            }
        }

        private int projectCount = 0;
        private int coveredProjects = 0;
        List<string> projectAssemblies = new List<string>();

        public List<DslToolsPackageInfo> GetExportablePackages() {
            List<DslToolsPackageInfo> list = new List<DslToolsPackageInfo>();
            using (RegistryKey key = Registry.LocalMachine.OpenSubKey(GetVsRunningHive() + @"DslTools\Packages\")) {
                foreach (string str in key.GetSubKeyNames()) {
                    try {
                        using (RegistryKey key2 = key.OpenSubKey(str)) {
                            DslToolsPackageInfo dslToolsPackageByGuid = GetDslToolsPackageByGuid(str, true);
                            if (dslToolsPackageByGuid != null) {
                                // Try loading the package type
                                GetPackageTypeMetaData(dslToolsPackageByGuid);
                                if (dslToolsPackageByGuid.IsExportable) {
                                    list.Add(dslToolsPackageByGuid);
                                }
                            }
                        }
                    }
                    catch (Exception ex) {
                        string error = ex.Message;
                    }
                }
            }
            return list;
        }

        public Type LoadPackageType(DslToolsPackageInfo packageInfo) {

            Assembly assembly = Assembly.Load(packageInfo.AssemblyName);
            if (assembly == null)
                return null;

            Type type = assembly.GetType(packageInfo.ClassName, false);
            return type;

        }

        public void GetPackageTypeMetaData(DslToolsPackageInfo packageInfo) {
            Type packageType = LoadPackageType(packageInfo);
            packageInfo.IsExportable = packageType != null;
        }


        public DslToolsPackageInfo GetDslToolsPackageByGuid(string packageGuid, bool ensureExportableMetadata) {
            DslToolsPackageInfo info = new DslToolsPackageInfo(packageGuid);
            using (RegistryKey key = Registry.LocalMachine.OpenSubKey(GetVsRunningHive() + @"DslTools\Packages\" + packageGuid)) {
                // Try to find the exportable attribute to see if we can use this package
                object exportableValue = key.GetValue("integration_exportable");
                int exportable = 0;
                bool exportableIsInt = exportableValue == null ? false : int.TryParse(exportableValue.ToString(), out exportable);
                if (!exportableIsInt || exportable != 1) {
                    return null;
                }
                string modelNamespace = key.GetValue("integration_namespace", string.Empty).ToString();
                string domainModelType = key.GetValue("integration_domainModelType", string.Empty).ToString();
                string domainModelMonikerResolverType = key.GetValue("integration_domainModelMonikerResolverType", string.Empty).ToString();
                string serializationHelperType = key.GetValue("integration_serializationHelperType", string.Empty).ToString();
                string serializationBehaviorType = key.GetValue("integration_serializationBehaviorType", string.Empty).ToString();
                string domainModelRootType = key.GetValue("integration_domainModelRootType", string.Empty).ToString();
                if (ensureExportableMetadata) {
                    if (string.IsNullOrEmpty(modelNamespace)) {
                        //throw new DslIntegrationServiceException(string.Format(Resources.UnableToFindRegistryValue, "namespace", packageGuid));
                    }
                    info.Namespace = modelNamespace;
                    if (string.IsNullOrEmpty(domainModelType)) {
                        //throw new DslIntegrationServiceException(string.Format(Resources.UnableToFindRegistryValue, "domainModelType", packageGuid));
                    }
                    info.DomainModelType = domainModelType;
                    if (string.IsNullOrEmpty(domainModelMonikerResolverType)) {
                        //throw new DslIntegrationServiceException(string.Format(Resources.UnableToFindRegistryValue, "domainModelMonikerResolverType", packageGuid));
                    }
                    info.DomainModelMonikerResolverType = domainModelMonikerResolverType;
                    if (string.IsNullOrEmpty(serializationHelperType)) {
                        //throw new DslIntegrationServiceException(string.Format(Resources.UnableToFindRegistryValue, "domainModelSerializerBehaviorType", packageGuid));
                    }
                    info.SerializationBehaviorType = serializationBehaviorType;
                    if (string.IsNullOrEmpty(serializationHelperType)) {
                        //throw new DslIntegrationServiceException(string.Format(Resources.UnableToFindRegistryValue, "modelElementRootType", packageGuid));
                    }
                    info.SerializationHelperType = serializationHelperType;
                    if (string.IsNullOrEmpty(domainModelRootType)) {
                        //throw new DslIntegrationServiceException(string.Format(Resources.UnableToFindRegistryValue, "modelElementRootType", packageGuid));
                    }
                    info.ModelElementRootType = domainModelRootType;
                }
            }
            info.FileExtension = "." + GetPackageEditorInformation(packageGuid);
            string registryGuidFormat = GetRegistryGuidFormat(packageGuid);
            using (RegistryKey key2 = Registry.LocalMachine.OpenSubKey(GetVsRunningHive() + @"Packages\" + registryGuidFormat)) {
                object assemblyValue = key2.GetValue("Assembly");
                if (assemblyValue == null) {
                    // Get the default value and extract the assembly
                    assemblyValue = key2.GetValue("");
                    if (assemblyValue.ToString().Contains(","))
                        assemblyValue = string.Join(",", assemblyValue.ToString().Split(','), 1, assemblyValue.ToString().Split(',').Length - 1);
                }
                info.AssemblyName = assemblyValue == null ? "" : assemblyValue.ToString();
                info.ClassName = key2.GetValue("Class").ToString();

            }
            return info;
        }


        public string GetPackageEditorInformation(string packageGuid) {
            using (RegistryKey key = Registry.LocalMachine.OpenSubKey(GetVsRunningHive() + @"Editors\")) {
                foreach (string str in key.GetSubKeyNames()) {
                    RegistryKey key2 = key.OpenSubKey(str);
                    if (packageGuid == key.OpenSubKey(str).GetValue("Package").ToString()) {
                        using (RegistryKey key3 = key2.OpenSubKey("Extensions")) {
                            return key3.GetValueNames().GetValue(0).ToString();
                        }
                    }
                }
            }
            return null;
        }

        public string GetRegistryGuidFormat(string guid) {
            guid = guid.StartsWith("{") ? guid : ("{" + guid);
            guid = guid.EndsWith("}") ? guid : (guid + "}");
            return guid;
        }

        public string GetVsRunningHive() {
            ILocalRegistry3 service = (ILocalRegistry3)DSLCrossModelFrameworkPackage.GetGlobalService(typeof(ILocalRegistry));
            string str = null;
            if ((service != null) && !ErrorHandler.Succeeded(service.GetLocalRegistryRoot(out str))) {
                return @"\Software\Microsoft\VisualStudio\9.0\";
            }
            if (string.IsNullOrEmpty(str)) {
                return @"\Software\Microsoft\VisualStudio\9.0\";
            }
            return (str + @"\");
        }




        public List<IModelFile> GetDslModels(IEnumerable<DslToolsPackageInfo> packageInfo) {
            List<IModelFile> modelFiles = new List<IModelFile>();


            // Search the current solution
            try {
                DTE service = DSLCrossModelFrameworkPackage.GetGlobalService(typeof(DTE)) as DTE;
                FindAllModelFiles(service.Solution, packageInfo, PassType.Analysis);
                modelFiles.AddRange(FindAllModelFiles(service.Solution, packageInfo, PassType.Solution));
                modelFiles.AddRange(FindAllModelFiles(service.Solution, packageInfo, PassType.Reference));
            }
            catch (Exception ex) {
                EnvironmentConfiguration.Current.LogMessage(string.Format("Error discovering model files: {0}", ex.Message));
                throw;
            }
            finally {
                ModelLoadingNotification(this, new ModelLoadingNotificationEventArgs(100));
            }

            foreach (IModelFile file in modelFiles) {
                EnvironmentConfiguration.Current.LogMessage(string.Format("Found Model File: {0} - {1}", file.GetType().Name, file.UniqueModelFileReference));
            }

            return modelFiles;
        }

        private List<IModelFile> FindAllModelFiles(Solution solution, IEnumerable<DslToolsPackageInfo> packageInfo, PassType passType) {
            List<IModelFile> modelFiles = new List<IModelFile>();
            modelFiles.AddRange(FindAllModelFiles(solution.Projects, packageInfo, passType));
            return modelFiles;
        }

        private List<IModelFile> FindAllModelFiles(Projects projects, IEnumerable<DslToolsPackageInfo> packageInfo, PassType passType) {
            List<IModelFile> modelFiles = new List<IModelFile>();

            foreach (Project project in projects) {
                // Ignore Miscellaneous Files folder
                if (project.Kind != "66A2671D-8FB5-11D2-AA7E-00C04F688DDE") {
                    // Get the model files in project items
                    if (project.Object is SolutionFolder) {
                        if (project.ProjectItems != null) {
                            modelFiles.AddRange(FindAllModelFiles(project.ProjectItems, packageInfo, passType));
                        }
                    }
                    modelFiles.AddRange(FindAllModelFilesInProject(project, packageInfo, passType));
                }
            }
            return modelFiles;
        }

        private List<IModelFile> FindAllModelFiles(ProjectItems items, IEnumerable<DslToolsPackageInfo> packageInfo, PassType passType) {
            List<IModelFile> modelFiles = new List<IModelFile>();
            foreach (ProjectItem item in items) {
                if (item.Object == null) {
                    IModelFile file = GetModelFile(item, packageInfo);
                    if (file != null) {
                        //file.ContainerType = ContainerType.SolutionFolder;
                        if(passType == PassType.Solution) {
                            modelFiles.Add(file);
                        }
                    }
                    continue;
                }
                Project project = item.Object as Project;
                if (project != null) {
                    if (project.Object is SolutionFolder) {
                        if (project.ProjectItems != null) {
                            modelFiles.AddRange(FindAllModelFiles(project.ProjectItems, packageInfo, passType));
                        }
                    }
                    else if (project.Object is VSProject) {
                        modelFiles.AddRange(FindAllModelFilesInProject(project, packageInfo, passType));
                    }
                    continue;
                }
                if ((item.ProjectItems != null) && (item.ProjectItems.Count > 0)) {
                    modelFiles.AddRange(FindAllModelFiles(item.ProjectItems, packageInfo, passType));
                }
                IModelFile modelFile = GetModelFile(item, packageInfo);
                if (modelFile != null) {
                    //modelFile.ContainerType = ContainerType.Project;
                    if(passType == PassType.Solution) {
                        modelFiles.Add(modelFile);
                        if (!projectAssemblies.Contains(modelFile.OwningAssembly)) {
                            projectAssemblies.Add(modelFile.OwningAssembly);
                        }
                    }
                }
            }

            return modelFiles;
        }

        private IModelFile GetModelFile(ProjectItem item, IEnumerable<DslToolsPackageInfo> packageInfo) {

            foreach(DslToolsPackageInfo package in packageInfo) {
                if ((package != null) && Path.GetFileName(item.Name).EndsWith(package.FileExtension)) {
                    if (IsTargetDsl(item, package)) {
                        return new SolutionModelFile(item, package);
                    }
                }
            }
            return null;
        }

        //private IList<IModelFile> GetModelFiles(Assembly assembly, DslToolsPackageInfo packageInfo, string referencingProject) {

        //    List<IModelFile> modelFiles = new List<IModelFile>();
        //    if (!projectAssemblies.Contains(assembly.GetName().Name)) {
        //        foreach (string resourceName in assembly.GetManifestResourceNames()) {
        //            if (resourceName.EndsWith(packageInfo.FileExtension)) {
        //                ReferenceModelFile modelFile =
        //                    new ReferenceModelFile(assembly, resourceName, referencingProject, packageInfo);
        //                modelFiles.Add(modelFile);
        //                projectAssemblies.Add(modelFile.OwningAssembly);
        //            }
        //        }
        //    }

        //    return modelFiles;
        //}

        private bool IsTargetDsl(ProjectItem item, DslToolsPackageInfo info) {
            //TODO: Implement IsTargetDsl Method
            return true;
        }

        private void NotifyProjectCovered() {
            coveredProjects++;
            if(ModelLoadingNotification != null && projectCount > 0) {
                int percentage = coveredProjects == 0 ? 0 : Convert.ToInt32((decimal)((decimal)coveredProjects / (decimal)projectCount) * 100);
                ModelLoadingNotification(this, new ModelLoadingNotificationEventArgs(percentage));
            }
        }

        private List<IModelFile> FindAllModelFilesInProject(Project project, IEnumerable<DslToolsPackageInfo> packageInfo, PassType passType) {

            List<IModelFile> modelFiles = new List<IModelFile>();
            if(passType == PassType.Analysis) {
                // Build up the count of projects. Add 2; 1 for solution pass and 1 for reference pass
                projectCount++;
                projectCount++;
            }
            else if(passType == PassType.Solution) {
                // Get the model files in project items
                if (project.ProjectItems != null) {
                    foreach (ProjectItem item in project.ProjectItems) {
                        if (item.Kind.Equals("{6BB5F8EF-4483-11D3-8BCF-00C04F8EC28C}")) {
                            modelFiles.AddRange(FindAllModelFiles(item.ProjectItems, packageInfo, passType));
                        }
                        IModelFile modelFile = GetModelFile(item, packageInfo);
                        if (modelFile != null) {
                            modelFiles.Add(modelFile);
                            if (!projectAssemblies.Contains(modelFile.OwningAssembly)) {
                                projectAssemblies.Add(modelFile.OwningAssembly);
                            }
                        }
                    }
                } 
                NotifyProjectCovered();
            }
            else if (passType == PassType.Reference) {
                // Get the model files in references
                if (project.Object is VSProject) {
                    foreach (Reference reference in ((VSProject)project.Object).References) {
                        try {
                            if (!string.IsNullOrEmpty(reference.Path) && File.Exists(reference.Path)) {
                                try {
                                    string owningAssembly = assemblyReferenceHelper.GetAssemblyName(reference.Path);
                                    if (!projectAssemblies.Contains(owningAssembly)) {
                                        List<IModelFile> assemblyModelFiles =
                                            assemblyReferenceHelper.GetModelFiles(reference.Path, packageInfo,
                                                                                  reference.ContainingProject.FullName);

                                        assemblyModelFiles.ForEach(delegate(IModelFile modelFile) {
                                            projectAssemblies.Add(modelFile.OwningAssembly);
                                            modelFiles.Add(modelFile);
                                        });
                                    }
                                }
                                catch (AssemblyReferenceLoadException e) {
                                    EnvironmentConfiguration.Current.LogMessage(e.ToString());
                                }
                            }
                        }
                        catch (FileNotFoundException) {
                            continue;
                        }
                        catch (FileLoadException) {
                            continue;
                        }
                    }
                }
                NotifyProjectCovered();
            }

            return modelFiles;
        }

        public ModelElement LoadModelRoot(Store store, DslToolsPackageInfo packageInfo, IModelFile modelFile) {
            Type modelType = Type.GetType(packageInfo.DomainModelType);
            Type serializationHelperType = Type.GetType(packageInfo.SerializationHelperType);
            object root = null;
            Type[] metaTypes = new Type[]
                {
                    typeof(CoreDesignSurfaceDomainModel),
                    modelType
                };

            // Load these types into the store, so that it knows about them for deserialization 
            store.LoadDomainModels(metaTypes);

            // Store the modelFileContents in a temp file
            string contents = modelFile.GetContents();
            string tempFileName = System.IO.Path.GetTempFileName();
            System.IO.File.WriteAllText(tempFileName, contents);


            // Deserialize the file into the store 
            try {
                using (Transaction t = store.TransactionManager.BeginTransaction("Reading diagram", true)) {
                    PropertyInfo instancePropertyInfo = serializationHelperType.GetProperty("Instance");
                    object instance = instancePropertyInfo.GetValue(null, null);

                    MethodInfo loadModelMethodInfo =
                        instance.GetType().GetMethod("LoadModel",
                                                     new Type[]
                                                         {
                                                             typeof (Store), typeof (string), typeof (ISchemaResolver),
                                                             typeof (ValidationController)
                                                         });

                    root = loadModelMethodInfo.Invoke(instance, new object[] {store, tempFileName, null, null});

                    t.Commit();
                }
            }catch(Exception ex) {
                throw;
            }

            return root as ModelElement;
        }

        public event EventHandler<ModelLoadingNotificationEventArgs> ModelLoadingNotification;

        private enum PassType {
            Analysis,
            Solution, 
            Reference
        }

        public void Dispose() {
            if(assemblyReferenceHelper != null) {
                assemblyReferenceHelper.Dispose();
                assemblyReferenceHelper = null;
            }
        }
    }
}